﻿using System.Collections.Generic;
using System.ComponentModel;
using MadMediaManager.WPFSupport.Interfaces.Services.Threading;

namespace MadMediaManager.WPFSupport.Services.Threading
{
    public class BackgroundWorkerService : IBackgroundWorkerService
    {
        private static readonly Dictionary<int, BackgroundWorker> Workers = new Dictionary<int, BackgroundWorker>();

        ~BackgroundWorkerService()
        {
            foreach (var worker in Workers)
            {
                worker.Value.CancelAsync();
            }
        }

        /// <summary>
        /// Registers the work and starts it asynchronously.
        /// </summary>
        /// <param name="work">The work to perform</param>
        /// <param name="workCompleted">The event to handle when the work is completed</param>
        /// <returns>A the hash code of the <see cref="BackgroundWorkerService"/> created.</returns>
        public int StartWork(DoWorkEventHandler work, RunWorkerCompletedEventHandler workCompleted)
        {
            return StartWork(work, null, workCompleted, null);
        }

        public int StartWork(DoWorkEventHandler work, object workArgument, RunWorkerCompletedEventHandler workCompleted)
        {
            return StartWork(work, workArgument, workCompleted, null);
        }

        public int StartWork(DoWorkEventHandler work, RunWorkerCompletedEventHandler workCompleted, ProgressChangedEventHandler progressChanged)
        {
            return StartWork(work, null, workCompleted, progressChanged);
        }

        public int StartWork(DoWorkEventHandler work, object workArgument, RunWorkerCompletedEventHandler workCompleted, ProgressChangedEventHandler progressChanged)
        {
            var worker = new BackgroundWorker();
            int workerHash = RegisterWorker(worker, work, workCompleted, progressChanged);

            worker.RunWorkerAsync();
            return workerHash;
        }

        private static int RegisterWorker(BackgroundWorker worker, DoWorkEventHandler work, RunWorkerCompletedEventHandler workCompleted,
            ProgressChangedEventHandler progressChanged)
        {
            int workerHash = worker.GetHashCode();
            worker.DoWork += work;
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += progressChanged;
            worker.RunWorkerCompleted += delegate { UnregisterWorker(workerHash, work, workCompleted, progressChanged); };
            worker.RunWorkerCompleted += workCompleted;

            worker.WorkerSupportsCancellation = true;
            lock (Workers)
            {
                Workers.Add(workerHash, worker);
            }

            return workerHash;
        }

        private static void UnregisterWorker(int workerHash, DoWorkEventHandler work, RunWorkerCompletedEventHandler workCompleted,
            ProgressChangedEventHandler progressChanged)
        {
            BackgroundWorker worker;
            lock (Workers)
            {
                if (Workers.TryGetValue(workerHash, out worker))
                {
                    worker.RunWorkerCompleted -= workCompleted;
                    worker.DoWork -= work;
                    worker.ProgressChanged -= progressChanged;
                    worker.Dispose();
                    Workers.Remove(workerHash);
                }
            }
        }

        public void CancelWork(int workerHash)
        {
            BackgroundWorker worker;
            lock (Workers)
            {
                if (Workers.TryGetValue(workerHash, out worker))
                {
                    worker.CancelAsync();
                }
            }
        }

        public void ReportProgress(int workerHash, int progressChanged, object userState)
        {
            BackgroundWorker worker;
            lock (Workers)
            {
                if (Workers.TryGetValue(workerHash, out worker))
                {
                    worker.ReportProgress(progressChanged, userState);
                }
            }
        }

        public void ReportProgress(int workerHash, int progressChanged)
        {
            ReportProgress(workerHash, progressChanged, null);
        }
    }
}